package com.tool.emulator.apple2.ui;

import com.tool.emulator.apple2.Apple2;
import com.tool.emulator.apple2.Monitor;
import com.tool.emulator.apple2.MonitorUI;
import com.tool.emulator.apple2.Util;
import com.tool.emulator.common.AccessUnit;

import java.awt.*;
import java.awt.image.MemoryImageSource;

/**
 * DisplayPanel.java @author tl 2011-6-23
 */
public class DisplayPanel extends Component implements MonitorUI {
	public static final int[] INDEXED_COLORS0 = {0x0, 0xD03, 0x009, 0xD2D, 0x072, 0x555, 0x22F, 0x6AF, 0x850, 0xF60, 0xAAA, 0xF98, 0x1D0, 0xFF0, 0x4F9, 0xFFF};
	public static final int[] INDEXED_COLORS = new int[16];
	static final int WIDTH = 280;
	static final int HEIGHT = 192;
	static final int TEXT_WIDTH = 7;
	static final int TEXT_COLS = 40;
	static final int HGR_COLS = 40;
	static final int TEXT_HEIGHT = 8;
	static final int TEXT_ROWS = 24;
	static final int MIXED_HEIGHT = 160;
	static final int MIXED_ROW = 20;
	static final long blank_time = 800L;
	public static int BLACK = INDEXED_COLORS[0];
	public static int MAGENTA = INDEXED_COLORS[3];
	public static int GREEN = INDEXED_COLORS[12];
	public static int WHITE = INDEXED_COLORS[15];
	public static int BLUE = INDEXED_COLORS[7];
	public static int ORANGE = INDEXED_COLORS[9];

	static {
		initializeColors(null);
	}

	final int[] data;
	final MemoryImageSource source;
	final Image img;
	final int[][] texts = new int[TEXT_ROWS][TEXT_COLS];
	final int[][] pixels = new int[HEIGHT][HGR_COLS];
	final int[][] pixels2 = new int[HEIGHT][WIDTH + 2];
	private volatile Apple2 apple2 = null;
	private volatile int current_mode = -1;
	private boolean modeChanged = true;

	public DisplayPanel() {
		this.data = new int[WIDTH * HEIGHT];
		for (int i = 0; i < data.length; i++) data[i] = 0xff000000;
		this.source = new MemoryImageSource(WIDTH, HEIGHT, data, 0, WIDTH);
		this.source.setAnimated(true);
		this.img = this.createImage(source);

	}

	private static void initializeColors(Color mono) {
		int rr = 0xff, gg = 0xff, bb = 0xff;
		if (mono != null) {
			rr = mono.getRed();
			gg = mono.getGreen();
			bb = mono.getBlue();
		}
		for (int i = 0; i < 16; i++) {
			int c3 = INDEXED_COLORS0[i];
			int r = 0xf & (c3 >> 8);
			int g = 0xf & (c3 >> 4);
			int b = 0xf & (c3 >> 0);
			r = r * 255 / 15;
			g = g * 255 / 15;
			b = b * 255 / 15;
			if (mono != null) {
				int ave = (r * 3 + g * 5 + b) / 9;
				r = Math.min(255, rr * ave / 255);
				g = Math.min(255, gg * ave / 255);
				b = Math.min(255, bb * ave / 255);
			}
			INDEXED_COLORS[i] = 0xff000000 | (r << 16) | (g << 8) | b;
		}
		BLACK = INDEXED_COLORS[0];
		MAGENTA = INDEXED_COLORS[3];
		GREEN = INDEXED_COLORS[12];
		WHITE = INDEXED_COLORS[15];
		BLUE = INDEXED_COLORS[7];
		ORANGE = INDEXED_COLORS[9];
	}

	private static int covertColor(int clr) {
		if (clr == 0xffffffff)
			clr = WHITE;
		return clr;
	}

	@Override
	public void paint(Graphics g) {
		g.drawImage(this.img, 0, 0, this.getWidth(), this.getHeight(), this);
	}

	private void setText(int r, int c, int val) {
		int addr = r * 8 * WIDTH + c * 7;
		int[][] bits = CharRom.getCharBits(val);
		for (int row = 0; row < 8; row++, addr += WIDTH) {
			for (int col = 0; col < 7; col++)
				this.data[addr + col] = covertColor(bits[row][col]);
		}
	}

	private void refreshText(boolean page2, int row0, int row1) {
		boolean blank = (System.currentTimeMillis() % blank_time) >= blank_time / 2;
		AccessUnit[] mem = apple2.MEM;
		ClipRect clip = new ClipRect();
		for (int row = row0; row < row1; row++) {
			int addr = Util.getTextAddr(page2, row);
			for (int col = 0; col < TEXT_COLS; col++) {
				int val = mem[addr++].directGet();
				if (!blank && (val >= 64 && val < 128)) val += 128;
				if (this.texts[row][col] != val || modeChanged) {
					this.texts[row][col] = val;
					this.setText(row, col, val);
					clip.updateText(row, col);
				}
			}
		}
		clip.updateImage();
	}

	private void refreshGR(boolean page2, int row0, int row1) {
		AccessUnit[] mem = apple2.MEM;
		ClipRect clip = new ClipRect();
		for (int row = row0; row < row1; row++) {
			int addr = Util.getTextAddr(page2, row);
			for (int col = 0; col < TEXT_COLS; col++) {
				int val = mem[addr++].directGet();
				int val0 = this.texts[row][col];
				if (val != val0 || modeChanged) {
					this.texts[row][col] = val;
					int hi = 0xf & (val >>> 4);
					int lo = 0xf & val;
					clip.fillRect(col * 7, row * 8, 7, 4, INDEXED_COLORS[lo]);
					clip.fillRect(col * 7, row * 8 + 4, 7, 4, INDEXED_COLORS[hi]);
				}
			}
		}
		clip.updateImage();
	}

	private void refreshHGR(boolean page2, int row0, int row1) {

		AccessUnit[] mem = apple2.MEM;
		ClipRect clip = new ClipRect();
		for (int row = row0; row < row1; row++) {
			int[] line = this.pixels[row];
			int[] line2 = this.pixels2[row];
			int addr = Util.getHGRAddr(page2, row);
			int x0 = 1;
			boolean changed = false;
			for (int col = 0; col < HGR_COLS; col++, x0 += 7) {
				int val = mem[addr++].directGet();
				if (line[col] != val || modeChanged) {
					line[col] = val;
					int x = x0;
					changed = true;
					boolean hbit = 0 != (val & 0x80);
					for (int i = 0; i < 7; i++, x++) {
						boolean odd = (x & 1) == 0;
						if (0 != (val & 1)) {
							if (hbit)
								line2[x] = odd ? ORANGE : BLUE;
							else
								line2[x] = odd ? GREEN : MAGENTA;
						} else {
							line2[x] = BLACK;
						}
						val >>>= 1;
					}
				}
			}
			if (changed) {
				line2[0] = line2[WIDTH] = BLACK;
				int off = row * WIDTH;
				int change_min = -1;
				int change_max = 0;
				for (int col = 1; col <= WIDTH; col++, off++) {
					int clr = line2[col];
					if (clr != BLACK && (line2[col - 1] != BLACK || line2[col + 1] != BLACK))
						clr = WHITE;
					if (data[off] != clr || modeChanged) {
						if (change_min < 0) change_min = col;
						change_max = col;
						data[off] = clr;
					}
				}
				if (change_min >= 0) {
					clip.update(change_min - 1, row, change_max, row + 1);
				}
			}
		}
		clip.updateImage();
	}

	public void changeColor(Color mono) {
		DisplayPanel.initializeColors(mono);
		this.current_mode = -1;
		this.modeChanged = true;
	}

	public void refresh() {
		int mode = apple2.MONITOR.getDisplayMode();
		boolean mixed = 0 != (mode & Monitor.MIXED_MODE);
		boolean page2 = 0 != (mode & Monitor.PAGE2);
		this.modeChanged = this.current_mode != mode;
		this.current_mode = mode;
		if (0 != (mode & Monitor.TEXT_MODE)) {
			this.refreshText(page2, 0, TEXT_ROWS);//ignore mixed mode
		} else {
			if (0 != (mode & Monitor.HGR_MODE))
				this.refreshHGR(page2, 0, mixed ? MIXED_HEIGHT : HEIGHT);
			else
				this.refreshGR(page2, 0, mixed ? MIXED_ROW : TEXT_ROWS);
			if (mixed)
				this.refreshText(false, MIXED_ROW, TEXT_ROWS);
		}
		if (this.modeChanged)
			source.newPixels();
		this.modeChanged = false;
	}

	public void refresh(Monitor m) {
		this.apple2 = m.getApple2();
		this.refresh();
	}

	public Object getViewComponent() {
		return this;
	}

	public void modeChanged(Monitor m) {
		this.current_mode = -1;
	}

	private class ClipRect {
		int min_x = WIDTH;
		int max_x = 0;
		int min_y = HEIGHT;
		int max_y = 0;

		void update(int minx, int miny, int maxx, int maxy) {
			if (modeChanged)
				return;
			if (minx < this.min_x) this.min_x = minx;
			if (miny < this.min_y) this.min_y = miny;
			if (maxx > this.max_x) this.max_x = maxx;
			if (maxy > this.max_y) this.max_y = maxy;
		}

		void fillRect(int x, int y, int w, int h, int clr) {
			int off = y * WIDTH + x;
			for (int i = 0; i < h; i++, off += WIDTH) {
				int off1 = off;
				for (int j = 0; j < w; j++)
					data[off1++] = clr;
			}
			this.update1(x, y, w, h);
		}

		void update1(int x, int y, int w, int h) {
			update(x, y, x + w, y + h);
		}

		void updateText(int row, int col) {
			this.update1(col * 7, row * 8, 7, 8);
		}

		int width() {
			return max_x - min_x;
		}

		int height() {
			return max_y - min_y;
		}

		void updateImage() {
			if (modeChanged)
				return;
			int w = this.width();
			int h = this.height();
			if (w > 0 && h > 0) {
				source.newPixels(this.min_x, this.min_y, w, h);
				//System.out.println("refresh image x=" + this.min_x + ", y=" + this.min_y + ",w=" + w + ",h=" + h);
			}
		}
	}

}
